//#define __FORCE_TRACE__
//#define __FORCE_DEBUG__
//#define __FORCE_INFO__
#define THIS_FILE "mso\common\fn_fsmloop.sqf"
#include <crbprofiler.hpp>
#include <core_macros.h>
/*
	Author: Nelson Duarte
	Modified by Tyrghen
	
	Description:
	Loop stacked code/function with timing and conditional control
	Code and conditions are executed in non-scheduled environment
	
	Parameters:
	_this select 0: Action
	_this select 1: Parameters
	
	Parameters:
		id				Used to remove the element afterwards
		execution		The name of the function to execute
		params			An array of parameters to pass to the function along these parameters
		timer			The number of units of time (X seconds or X runs)
		timerType		The type of units of time ("seconds","runs")
		conditionStart	The function name defining if the code can be executed
		conditionEnd	The function name defining if the code should be stopped
		executeOnce		A boolean defining if this is the last execution
		isSpawn			A boolean indicating if we should spawn the function or not.
						Note that if it still runs from a previous call, it won't be started again!
	
	Returns:
	NOTHING
	
	Examples:
	["initialize"] call mso_fnc_fsmloop; 										//Initializes game loop (not required if AUTO_INITIALIZE is set to true)
	["isInitialized"] call mso_fnc_fsmloop; 										//returns true if the loop is currently initialized
	["terminate"] call mso_fnc_fsmloop; 										//Terminates game loop (not required if AUTO_TERMINATE is set to true)
	["itemAdd", ["uniqueId", "tmf_fnc_displayTime", 5]] call mso_fnc_fsmloop;					//Hints time every five seconds
	["itemAdd", ["uniqueId", "tmf_fnc_displayTime", 120, "runs"]] call mso_fnc_fsmloop; 				//Hints time every 120 runs
	["itemAdd", ["uniqueId", "tmf_fnc_displayTime", nil, nil, "tmf_fnc_startDisplay"]] call mso_fnc_fsmloop; 	//Hints time every frame whenever tmf_fnc_startDisplay returns true
	["itemAdd", ["uniqueId", "tmf_fnc_displayTime", 5, "seconds", "tmf_fnc_startDisplay"]] call mso_fnc_fsmloop; 	//Hints time every five seconds whenever tmf_fnc_startDisplay returns true
	["itemAdd", ["uniqueId", "tmf_fnc_displayTime"]] call mso_fnc_fsmloop; 						//Hints time every frame
	["itemRemove", ["uniqueId"]] call mso_fnc_fsmloop; 								//Remove item from loop with id "uniqueId"
	["uid"] call mso_fnc_fsmloop; 								//Returns a unique ID you can use as a handle for an event to add. It is guaranteed to be unique on the local system
*/

CRBPROFILERSTART("mso_fnc_fsmloop")

TRACE_1("Start",_this);

//Parameters
private ["_action", "_parameters","_result"];
_action		= [_this, 0, ""] call BIS_fnc_param;
_parameters	= [_this, 1, []] call BIS_fnc_param;
_result = true;
switch (toLower _action) do {
	case (toLower "initialize") : {
		//If initialized already, we terminate current instance and initialize a new one
		//All data will be lost
		if (!isNil { missionNameSpace getVariable QGVAR(fnc_fsmloop_initialized) }) then {
			//Terminate
			["terminate"] call FUNC(fsmloop);
		};
				
		if (isNil { missionNameSpace getVariable QGVAR(fnc_fsmloop_handle) } || {completedFSM (missionNameSpace getVariable QGVAR(fnc_fsmloop_handle))}) then {
			missionNameSpace setVariable [QGVAR(fnc_fsmloop_handle),(execFSM "tmf\core\fsms\ExecutionLoop.fsm")];
		};
		
		//Queue
		missionNameSpace setVariable [QGVAR(fnc_fsmloop_queue), []];
		
		missionNameSpace setVariable [QGVAR(fnc_fsmloop_remove_queue),[]];
		
		//The current amount of elapsed frames
		missionNameSpace setVariable [QGVAR(fnc_fsmloop_executions), 0];
		
		//The current time
		missionNameSpace setVariable [QGVAR(fnc_fsmloop_time), time];
		
		//Flag
		missionNameSpace setVariable [QGVAR(fnc_fsmloop_initialized), true];
		
		//Log
		"Initialized" call BIS_fnc_log;
	};
	
	case (toLower "terminate") : {
		//Clear variables
		missionNameSpace setVariable [QGVAR(fnc_fsmloop_initialized), nil];
		missionNameSpace setVariable [QGVAR(fnc_fsmloop_queue), []];
		missionNameSpace setVariable [QGVAR(fnc_fsmloop_executions), 0];
		missionNameSpace setVariable [QGVAR(fnc_fsmloop_handle),nil];
		//Log
		"Terminated" call BIS_fnc_log;
	};
	
	case (toLower "execute") : {
		//Initialized?
		if (isNil { missionNameSpace getVariable QGVAR(fnc_fsmloop_initialized) }) exitWith {
			//Error
			["Action (%1) requires initialization", _action] call BIS_fnc_error;
		};
				
		if (isNil { missionNameSpace getVariable QGVAR(fnc_fsmloop_handle) } || {completedFSM (missionNameSpace getVariable QGVAR(fnc_fsmloop_handle))}) then {
			missionNameSpace setVariable [QGVAR(fnc_fsmloop_handle),(execFSM "tmf\core\fsms\ExecutionLoop.fsm")];
		};
		
		private ["_executions","_remove_queue","_index","_start"];
		//Update frames and time
		_executions = (missionNameSpace getVariable QGVAR(fnc_fsmloop_executions)) + 1;
		missionNameSpace setVariable [QGVAR(fnc_fsmloop_executions), _executions];
		missionNameSpace setVariable [QGVAR(fnc_fsmloop_time), time];
		DEBUG_1("Start queue",ARR_2(_executions,count (missionNameSpace getVariable QGVAR(fnc_fsmloop_queue))));
		_start = time;
		
		_remove_queue = (missionNameSpace getVariable [QGVAR(fnc_fsmloop_remove_queue),[]]);
		missionNameSpace setVariable [QGVAR(fnc_fsmloop_remove_queue),[]];
		// Remove all items queued for removal
		if (count _remove_queue > 0) then {
			{
				private ["_id","_queue"];
				_queue = missionNameSpace getVariable QGVAR(fnc_fsmloop_queue);
				_id = _x;
				//Go through queue and find matching id
				{
					//The current item id
					private "_itemId";
					_itemId = [_x, 0, "", [""]] call BIS_fnc_param;
					//Match found?
					if (_id == _itemId) then {
						_queue set [_forEachIndex, "delete"];
					};
				} forEach _queue;
				//Now we remove all dirty items from the queue
				while { "delete" in _queue } do {
					_queue = _queue - ["delete"];
				};
				//Store queue
				missionNameSpace setVariable [QGVAR(fnc_fsmloop_queue), _queue];
			} foreach _remove_queue;
		};
		DEBUG_1("Executing items",ARR_3(_executions,count (missionNameSpace getVariable QGVAR(fnc_fsmloop_queue)),_start));
		//Go through all items in queue
		for "_index" from 0 to (count(missionNameSpace getVariable QGVAR(fnc_fsmloop_queue)) - 1) step 1 do  {
			private ["_parameters","_item_start"];
			_item_start = time;
			_parameters = (missionNameSpace getVariable QGVAR(fnc_fsmloop_queue)) select _index;
			_parameters set [10, _index];
			
			//Execute item
			["itemExecute", _parameters] call FUNC(fsmloop);
			DEBUG_1("Executed",ARR_2(time - _item_start,_parameters));
		};
		DEBUG_1("All items executed",ARR_3(_executions,count (missionNameSpace getVariable QGVAR(fnc_fsmloop_queue)),time - _start));
		if (count (missionNameSpace getVariable QGVAR(fnc_fsmloop_queue)) <= 0) then {
			["terminate"] call FUNC(fsmloop);
		};
	};
	
	case (toLower "itemExecute") : {
		//Initialized?
		if (isNil { missionNameSpace getVariable QGVAR(fnc_fsmloop_initialized) }) exitWith {
			//Error
			["Action (%1) requires initialization", _action] call BIS_fnc_error;
		};
		
		//Parameters
		private ["_id", "_code", "_params", "_timer", "_timerType", "_conditionStart", "_conditionEnd", "_executeOnce", "_lastExecutionTime", "_queue_index","_isSpawn","_start","_end","_handle"];
		_id 				= GETVAL(_parameters, 0, "");
		_code 				= GETVAL(_parameters, 1, "");
		_params				= GETVAL(_parameters, 2, []);
		_timer				= GETVAL(_parameters, 3, 0);
		_timerType 			= GETVAL(_parameters, 4, "seconds");
		_conditionStart		= GETVAL(_parameters, 5, "");
		_conditionEnd		= GETVAL(_parameters, 6, "");
		_executeOnce 		= GETVAL(_parameters, 7, false);
		_isSpawn 			= GETVAL(_parameters, 8, false);
		_lastExecutionTime 	= GETVAL(_parameters, 9, time);
		_queue_index		= GETVAL(_parameters, 10, 0);
		_handle				= GETVAL(_parameters, 11, false);
		
		//The condition, depends on timerType
		private "_conditionTimer";
		_conditionTimer = if (_timerType == "runs") then {
			(missionNameSpace getVariable QGVAR(fnc_fsmloop_executions)) >= (_lastExecutionTime + _timer);
		} else {
			time >= (_lastExecutionTime + _timer);
		};

		//Should we execute the item?
		if (_conditionTimer) then {
			_start = true;
			if (_conditionStart != "") then {
				_start = _parameters call (missionNameSpace getVariable [_conditionStart,{false}]);
			};
			if (!(isNil "_start") && {IS_BOOL(_start)} && {_start}) then {
				_end = false;
				//Process item
				// We ignore items which are not strings designating a function
				if (IS_STRING(_code)) then {
					if (_isSpawn) then {
						// We only execute the code if the previous iteration already ended
						if (typename _handle == typename true || {scriptDone _handle}) then {
							_handle = _parameters spawn (missionNameSpace getVariable [_code,{}]);
							TRACE_1("Item Spawned",_parameters);
						};
					} else {
						_parameters call (missionNameSpace getVariable [_code,{}]);
						TRACE_1("Item Called",_parameters);
					};
				} else {
					// This automatically removes non string items
					_end = true;
				};
				
				// We check for the ending condition if there is a function provided
				if (!_end && _conditionEnd != "") then {
					_end = _parameters call (missionNameSpace getVariable [_conditionEnd,{true}]);
				};
				
				//Should only execute once or end condition met?
				if (_executeOnce || _end) then {
					//Remove item
					["itemRemove", _id] call FUNC(fsmloop);
				} else {
					//Queue
					private "_queue";
					_queue = missionNameSpace getVariable QGVAR(fnc_fsmloop_queue);
					
					//Item
					private "_item";
					_item = _queue select _queue_index;
					
					//Update timer/frames
					if (_timerType == "runs") then {
						_item set [9, missionNameSpace getVariable QGVAR(fnc_fsmloop_executions)];
					} else {
						_item set [9, time];
					};
					if (_isSpawn) then {
						_item set [11, _handle];
					};
					//Modify item in queue
					_queue set [_queue_index, _item];
					TRACE_2("Updated Item",_queue_index,_item);
					//Store
					missionNameSpace setVariable [QGVAR(fnc_fsmloop_queue), _queue];
				};
			} else {
				TRACE_1("Cannot start",ARR_2(_id,_code));
			};
		} else {
			TRACE_1("Too early",ARR_4(_id,_code,_lastExecutionTime,_timer));
		};
	};
	
	case (toLower "itemAdd") : {
		//Public
		private ["_id", "_code", "_params", "_timer", "_timerType", "_conditionStart", "_conditionEnd", "_executeOnce","_isSpawn","_runNow","_lastExecution","_array"];
		_id 			= GETVAL(_parameters, 0, "");
		_code 			= GETVAL(_parameters, 1, "");
		_params			= GETVAL(_parameters, 2, []);
		_timer			= GETVAL(_parameters, 3, 0);
		_timerType 		= GETVAL(_parameters, 4, "seconds");
		_conditionStart	= GETVAL(_parameters, 5, "");
		_conditionEnd	= GETVAL(_parameters, 6, "");
		_executeOnce 	= GETVAL(_parameters, 7, false);
		_runNow 		= GETVAL(_parameters, 8, false);
		_isSpawn 		= GETVAL(_parameters, 9, false);
		
		//Initialize loop if not initialized already
		if (isNil { missionNameSpace getVariable QGVAR(fnc_fsmloop_initialized) }) then {
			//Auto initialize
			["initialize"] call FUNC(fsmloop);
		};
		if (!(IS_STRING(_id)) || {_id == ""}) exitWith { ["Invalid type or content for element id: %1", typename _id] call BIS_fnc_error; };
		if (!(IS_STRING(_code))) exitWith { ["Invalid type for execution function: %1", typename _code] call BIS_fnc_error; };
		if (!(IS_STRING(_conditionStart))) exitWith { ["Invalid type for start condition function: %1", typename _conditionStart] call BIS_fnc_error; };
		if (!(IS_STRING(_conditionEnd))) exitWith { ["Invalid type for end condition function: %1", typename _conditionEnd] call BIS_fnc_error; };
		if (isNil { missionNameSpace getVariable _code }) exitWith {
			["Unknown execution function on this instance: %1", _code] call BIS_fnc_error;
		};
		if ((_conditionStart != "") && {isNil { missionNameSpace getVariable _conditionStart }}) exitWith {
			["Unknown start function on this instance: %1", _conditionStart] call BIS_fnc_error;
		};
		if ((_conditionEnd != "") && {isNil { missionNameSpace getVariable _conditionEnd }}) exitWith {
			["Unknown end function on this instance: %1", _conditionEnd] call BIS_fnc_error;
		};
		if (!(IS_STRING(_timerType))) then { _timerType = "seconds"; };
		_timerType = switch(toLower(_timerType)) do {
			case "seconds": { "seconds" };
			case "secondes": { "seconds" };
			case "second": { "seconds" };
			case "seconde": { "seconds" };
			case "sec": { "seconds" };
			case "runs": { "runs" };
			case "run": { "runs" };
			case "execution": { "runs" };
			case "exec": { "runs" };
			case "cycle": { "runs" };
			default { "seconds" };
		};
		if (!(IS_BOOL(_executeOnce))) then { _executeOnce = false; };
		if (!(IS_BOOL(_runNow))) then { _runNow = false; };
		if (!(IS_BOOL(_isSpawn))) then { _isSpawn = false; };
		
		_lastExecution = time;
		// We change the last Execution time to make sure it runs on the next frame
		if (_runNow) then { _lastExecution = time - (_timer max 1); };
		
		_array = [_id, _code, _params, _timer, _timerType, _conditionStart, _conditionEnd, _executeOnce, _isSpawn, _lastExecution, 0];
		if (_isSpawn) then {
			// In the special case of Spawn scripts, we add a terminated handle
			_array set [count _array,(0 spawn {})];
		};
		TRACE_1("Queue Item",_array);
		
		//Queue
		private "_queue";
		_queue = missionNameSpace getVariable QGVAR(fnc_fsmloop_queue);
		//Add item to queue
		_queue set [count _queue, _array];
		//Store queue
		missionNameSpace setVariable [QGVAR(fnc_fsmloop_queue), _queue];
		
		_result = (missionNameSpace getVariable [QGVAR(fnc_fsmloop_counter),0]) + 1;
		missionNameSpace setVariable [QGVAR(fnc_fsmloop_counter),_result];
		
		//Log
		["Item added: Id (%1), Code (%2), Timer (%3), Timer Type (%4), Condition Start (%5), Condition End (%6), Execute Once (%7), Run Now (%8/%9), Is Spawn (%10)", _id, _code, _timer, _timerType, _conditionStart, _conditionEnd, _executeOnce,_runNow,_lastExecution,_isSpawn] call BIS_fnc_logFormat;
	};
	
	case (toLower "itemRemove") : {
		//Initialized?
		if (isNil { missionNameSpace getVariable QGVAR(fnc_fsmloop_initialized) }) exitWith {
			//Error
			["Action (%1) requires initialization", _action] call BIS_fnc_error;
		};
		
		//Parameters
		private ["_id"];
		_id = [_parameters, 0, "", [""]] call BIS_fnc_param;
		
		//Queue
		private "_queue";
		_queue = missionNameSpace getVariable QGVAR(fnc_fsmloop_remove_queue);
		_queue set [count _queue, _id];
		missionNameSpace setVariable [QGVAR(fnc_fsmloop_remove_queue), _queue];
		
		//Log
		["Item removed: Id (%1)", _id] call BIS_fnc_logFormat;
	};
	
	case (toLower "isinitialized") : {
		//Initialized?
		_result = (isNil { missionNameSpace getVariable QGVAR(fnc_fsmloop_initialized) });
	};
		
	case (toLower "uid") : {
		_result = (missionNameSpace getVariable [QGVAR(fnc_fsmloop_counter),0]) + 1;
		missionNameSpace setVariable [QGVAR(fnc_fsmloop_counter),_result];
		_result = format["fsmloop_%1",_result];
	};

	case DEFAULT {
		//Error
		["Invalid action: %1", _action] call BIS_fnc_error;
	};
};

CRBPROFILERSTOP
_result;